package com.ib.algokit;

import static org.activequant.util.calendar.CalendarUtils.addDays;
import static org.activequant.util.calendar.CalendarUtils.isBusinessDay;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import org.activequant.broker.IOrderTracker;
import org.activequant.calendar.ExchangeCalendar;
import org.activequant.calendar.HolidayCalendar;
import org.activequant.calendar.IExchangeCalendar;
import org.activequant.calendar.NASDAQCalendarSettings;
import org.activequant.calendar.USMarketsHolidayCalendarSettings;
import org.activequant.core.domainmodel.InstrumentSpecification;
import org.activequant.core.domainmodel.SeriesSpecification;
import org.activequant.core.domainmodel.account.Order;
import org.activequant.core.domainmodel.account.Portfolio;
import org.activequant.core.domainmodel.data.Candle;
import org.activequant.core.domainmodel.data.CandleSeries;
import org.activequant.core.domainmodel.data.MarketDepthEventSeries;
import org.activequant.core.domainmodel.data.Quote;
import org.activequant.core.domainmodel.data.QuoteSeries;
import org.activequant.core.domainmodel.data.TickerNews;
import org.activequant.core.domainmodel.data.TickerNewsSeries;
import org.activequant.core.domainmodel.data.TradeIndication;
import org.activequant.core.domainmodel.data.TradeIndicationSeries;
import org.activequant.core.domainmodel.marketdepth.MarketDepthEvent;
import org.activequant.core.domainmodel.marketscanner.InstrumentSpecificationDetails;
import org.activequant.core.domainmodel.marketscanner.MarketScan;
import org.activequant.core.domainmodel.marketscanner.MarketScanSeries;
import org.activequant.core.domainmodel.marketscanner.MarketScanSeriesSpecification;
import org.activequant.core.domainmodel.marketscanner.ScanCriteria;
import org.activequant.core.types.OrderSide;
import org.activequant.core.types.OrderType;
import org.activequant.core.types.SecurityType;
import org.activequant.core.types.TimeFrame;
import org.activequant.core.types.TimeStamp;
import org.activequant.core.util.TimeSeriesUtils;
import org.activequant.core.util.TimeStampFormat;
import org.activequant.data.retrieval.ISubscription;
import org.activequant.math.algorithms.FinancialLibrary;
import org.activequant.math.algorithms.Interval;
import org.activequant.tradesystem.ib.ClosePositionService;
import org.activequant.tradesystem.ib.IPositionService;
import org.activequant.tradesystem.ib.IPositionService.IPositionTracker;
import org.activequant.tradesystem.ib.SeriesSpecHelper;
import org.activequant.tradesystem.types.Mode;
import org.activequant.util.pattern.events.IEventListener;
import org.apache.log4j.Logger;

import com.ib.algokit.forex.CurrencyPairs;
import com.ib.algokit.ib.CommissionsCalculator;
import com.ib.algokit.ib.IBPortfolioManagingBroker;
import com.ib.algokit.ib.IBPortfolioManagingBroker.IBPosition;

/**
 * Holds the following associated variables:
 * <ul>
 * <li>ema(TraderProxy)</li>
 * <li>instrumentSpecification(InstrumentSpecification)</li>
 * <li>instrumentSpecificationDetails(ArrayList&lt;InstrumentSpecificationDetails&gt;)</li>
 * <li>exchangeCalendar(ExchangeCalendar)</li>
 * <li>exchangeTradingHours_T_MINUS_5(Map&lt;ExchangeCalendar,Map&lt;TimeStamp,Interval&lt;TimeStamp&gt;&gt;&gt;)</li>
 * <li>exchangeTradingRange_T_MINUS_5(Map&lt;ExchangeCalendar,Interval&lt;TimeStamp&gt;&gt;)</li>
 * <li>exchangeTradingRange_T_MINUS_1Y(Map&lt;ExchangeCalendar,Interval&lt;TimeStamp&gt;&gt;)</li>
 * <li>historicalCandleSeriesCache(Map&lt;SeriesSpecification,CandleSeries&gt;)</li>
 * <li>historicalQuoteSeriesCache(Map&lt;SeriesSpecification,QuoteSeries&gt;)</li>
 * <li>state(InstrumentAnalyzerState)</li>
 * <li>analyzerPlugin(AnalyzerPluginInterface)</li>
 * <li>instrumentAnalyzers(Map&lt;InstrumentSpecification, InstrumentAnalyzer&gt;)</li>
 * <li>enabled(boolean)</li>
 * <li>liveCandleSubscription(ISubscription&lt;Candle&gt; )</li>
 * <li>liveQuoteSubscription(ISubscription&lt;Quote&gt;)</li>
 * <li>liveTradeIndicationSubscription(ISubscription&lt;TradeIndication&gt;)</li>
 * <li>liveMarketDepthSubscription(ISubscription&lt;MarketDepth&gt;)</li>
 * <li>liveCandleSeriesBag(AtomicReference&lt;CandleSeries&gt;)</li>
 * <li>liveQuoteSeriesBag(AtomicReference&lt;QuoteSeries&gt;)</li>
 * <li>liveTradeIndicationSeriesBag(AtomicReference&lt;TradeIndicationSeries&gt;)</li>
 * <li>liveTradeIndicationSeriesBag(AtomicReference&lt;MarketDepthSeries&gt;)</li>
 * <li>positionService(IPositionService)</li>
 * <li>fmt(TimeStampFormat)</li>
 * <li>quoteFeedListener(IEventListener&lt;Quote&gt;)</li>
 * <li>candleFeedlistener(IEventListener&lt;Candle&gt;)</li>
 * <li>tradeIndicationFeedListener(IEventListener&lt;TradeIndication&gt;)</li>
 * <li>marketDepthFeedListener(IEventListener&lt;MarketDepth&gt;)</li>
 * <li>position(IBPosition)</li>
 * </ul>
 * @author Dimitar
 *
 */
public class InstrumentAnalyzer {
	
	protected final static Logger log = Logger.getLogger(InstrumentAnalyzer.class);
	/**
	 * private final EMA ema;
	 */
	private final TraderProxy ema;
	/**
	 * private final InstrumentSpecification instrumentSpecification;
	 */
	private final InstrumentSpecification instrumentSpecification;
	/**
	 * private ArrayList&lt;InstrumentSpecificationDetails&gt; instrumentSpecificationDetails;
	 */
	private ArrayList<InstrumentSpecificationDetails> instrumentSpecificationDetails;
	/**
	 * private final ExchangeCalendar exchangeCalendar = new ExchangeCalendar(NASDAQCalendarSettings.getInstance(), new HolidayCalendar(USMarketsHolidayCalendarSettings.getInstance()));
	 */
	private final IExchangeCalendar exchangeCalendar = new ExchangeCalendar(NASDAQCalendarSettings.getInstance(), new HolidayCalendar(USMarketsHolidayCalendarSettings.getInstance()));		
	/**
	 * private final static Map&lt;IExchangeCalendar,Map&lt;TimeStamp,Interval&lt;TimeStamp&gt;&gt;&gt; exchangeTradingHours_T_MINUS_5 = new HashMap&lt;IExchangeCalendar,Map&lt;TimeStamp,Interval&lt;TimeStamp&gt;&gt;&gt;();<br/>
	 * caching these per exchange makes sense
	 */
	private final static Map<IExchangeCalendar,Map<TimeStamp,Interval<TimeStamp>>> exchangeTradingHours_T_MINUS_5 = new HashMap<IExchangeCalendar,Map<TimeStamp,Interval<TimeStamp>>>();
	/**
	 * private final static Map&lt;IExchangeCalendar,Interval&lt;TimeStamp&gt;&gt; exchangeTradingRange_T_MINUS_5 = new HashMap&lt;IExchangeCalendar,Interval&lt;TimeStamp&gt;&gt;();<br/>
	 * caching these per exchange makes sense
	 */
	private final static Map<IExchangeCalendar,Interval<TimeStamp>> exchangeTradingRange_T_MINUS_5 = new HashMap<IExchangeCalendar,Interval<TimeStamp>>();
	/**
	 * private final static Map&lt;IExchangeCalendar,Interval&lt;TimeStamp&gt;&gt; exchangeTradingRange_T_MINUS_30 = new HashMap&lt;IExchangeCalendar,Interval&lt;TimeStamp&gt;&gt;();<br/>
	 * caching these per exchange makes sense
	 */
	private final static Map<IExchangeCalendar,Interval<TimeStamp>> exchangeTradingRange_T_MINUS_30 = new HashMap<IExchangeCalendar,Interval<TimeStamp>>();
	/**
	 * private final static Map&lt;IExchangeCalendar,Interval&lt;TimeStamp&gt;&gt; exchangeTradingRange_T_MINUS_1Y = new HashMap&lt;IExchangeCalendar,Interval&lt;TimeStamp&gt;&gt;();<br/>
	 * caching these per exchange makes sense
	 */
	private final static Map<IExchangeCalendar,Interval<TimeStamp>> exchangeTradingRange_T_MINUS_1Y = new HashMap<IExchangeCalendar,Interval<TimeStamp>>();	
	/**
	 * private final Map&lt;SeriesSpecification,CandleSeries&gt; historicalCandleSeriesCache = new HashMap&lt;SeriesSpecification,CandleSeries&gt;();
	 */
	private final Map<SeriesSpecification,CandleSeries> historicalCandleSeriesCache = new HashMap<SeriesSpecification,CandleSeries>();
	/**
	 * private final Map&lt;SeriesSpecification,QuoteSeries&gt; historicalQuoteSeriesCache = new HashMap&lt;SeriesSpecification,QuoteSeries&gt;();
	 */
	private final Map<SeriesSpecification,QuoteSeries> historicalQuoteSeriesCache = new HashMap<SeriesSpecification,QuoteSeries>();
	/**
	 * private final Map&lt;SeriesSpecification,TickerNewsSeries&gt; historicalTickerNewsSeriesCache = new HashMap&lt;SeriesSpecification,TickerNewsSeries&gt;();
	 */
	private final Map<SeriesSpecification,TickerNewsSeries> historicalTickerNewsSeriesCache = new HashMap<SeriesSpecification,TickerNewsSeries>();
	/**
	 * private final static Map&lt;MarketScanSeriesSpecification,MarketScanSeries&gt; historicalMarketScanCache = new HashMap&lt;MarketScanSeriesSpecification,MarketScanSeries&gt;();
	 */
	private final static Map<MarketScanSeriesSpecification,MarketScanSeries> historicalMarketScanCache = new HashMap<MarketScanSeriesSpecification,MarketScanSeries>();
	/**
	 * private final Map&lt;MarketScanSeriesSpecification,MarketScanSeries&gt; historicalMarketScanCacheForThisInstrument = new HashMap&lt;MarketScanSeriesSpecification,MarketScanSeries&gt;();
	 */
	private final Map<MarketScanSeriesSpecification,MarketScanSeries> historicalMarketScanCacheForThisInstrument = new HashMap<MarketScanSeriesSpecification,MarketScanSeries>();
	/**
	 * private final InstrumentAnalyzerState state;
	 */
	private final InstrumentAnalyzerState state;
	/**
	 * private final AnalyzerPluginInterface analyzerPlugin;
	 */
	private final AnalyzerPluginInterface analyzerPlugin;
	/**
	 * private static final Map&lt;InstrumentSpecification, InstrumentAnalyzer&gt; instrumentAnalyzers = new ConcurrentHashMap&lt;InstrumentSpecification, InstrumentAnalyzer&gt;();
	 */
	private static final Map<InstrumentSpecification, InstrumentAnalyzer> instrumentAnalyzers = new ConcurrentHashMap<InstrumentSpecification, InstrumentAnalyzer>();
	/**
	 * private boolean enabled;
	 */
	private boolean enabled;
	/**
	 * private ISubscription&lt;Candle&gt; liveCandleSubscription;
	 */
	private ISubscription<Candle> liveCandleSubscription;
	/**
	 * private ISubscription&lt;Quote&gt; liveQuoteSubscription;
	 */
	private ISubscription<Quote> liveQuoteSubscription;
	/**
	 * private ISubscription&lt;TradeIndication&gt; liveTradeIndicationSubscription;
	 */
	private ISubscription<TradeIndication> liveTradeIndicationSubscription;
	/**
	 * private ISubscription&lt;MarketDepth&gt; liveMarketDepthEventSubscription;
	 */
	private ISubscription<MarketDepthEvent> liveMarketDepthEventSubscription;
	/**
	 * private ISubscription&lt;TickerNews&gt; liveTickerNewsSubscription;
	 */
	private ISubscription<TickerNews> liveTickerNewsSubscription;
	/**
	 * private final AtomicReference&lt;CandleSeries&gt; liveCandleSeriesBag = new AtomicReference&lt;CandleSeries&gt;();
	 */
	private final AtomicReference<CandleSeries> liveCandleSeriesBag = new AtomicReference<CandleSeries>();
	/**
	 * private final AtomicReference&lt;QuoteSeries&gt; liveQuoteSeriesBag = new AtomicReference&lt;QuoteSeries&gt;();
	 */
	private final AtomicReference<QuoteSeries> liveQuoteSeriesBag = new AtomicReference<QuoteSeries>();
	/**
	 * private final AtomicReference&lt;TradeIndicationSeries&gt; liveTradeIndicationSeriesBag = new AtomicReference&lt;TradeIndicationSeries&gt;();
	 */
	private final AtomicReference<TradeIndicationSeries> liveTradeIndicationSeriesBag = new AtomicReference<TradeIndicationSeries>();
	/**
	 * private final AtomicReference&lt;MarketDepthEventSeries&gt; liveMarketDepthEventSeriesBag = new AtomicReference&lt;MarketDepthEventSeries&gt;();
	 */
	private final AtomicReference<MarketDepthEventSeries> liveMarketDepthEventSeriesBag = new AtomicReference<MarketDepthEventSeries>();
	/**
	 * private final AtomicReference&lt;TickerNewsSeries&gt; liveTickerNewsSeriesBag = new AtomicReference&lt;TickerNewsSeries&gt;();
	 */
	private final AtomicReference<TickerNewsSeries> liveTickerNewsSeriesBag = new AtomicReference<TickerNewsSeries>();
	/**
	 * private IPositionService positionService;
	 */
	private IPositionService positionService;
	/**
	 * private IPositionTracker positionTracker=null;
	 */
	private IPositionTracker positionTracker=null;
	/**
	 * private IBPosition position;
	 */
//	private IBPosition position;
	/**
	 * private AtomicBoolean hasRequestedToGoLong = new AtomicBoolean();
	 */
	private AtomicBoolean hasRequestedToGoLong = new AtomicBoolean();
	
	boolean useDatabaseForSpecDetails=true;
	
	boolean useDatabaseFor1YHistoricData=true;
	
	boolean useDatabaseFor5DHistoricData=true;

	boolean useDatabaseFor30DHistoricData=true;
	
	boolean useDatabaseForRealTimeCandles=true;
	
	boolean useDatabaseForRealTimeQuotes=true;
	
	boolean useDatabaseForRealTimeTradeIndication=true;
	
	boolean useDatabaseForRealTimeMarketDepth=true;
	
	boolean useDatabaseForRealTimeTickerNews=true;
	/**
	 * Constructs an InstrumentAnalyzer using the given ema(EMA), instrumentSpecification(InstrumentSpecification) and position(IBPosition) to set its associated ema(EMA), instrumentSpecification(InstrumentSpecification) and position(IBPosition).<br/>
	 * It also initializes its associated analyzerPlugin(AnalyzerPluginInterface), state(InstrumentAnalyzerState) and positionService(IPositionService).<br/>
	 * It then looks at the associated instrumentAnalyzers(Map&lt;InstrumentSpecification,InstrumentAnalyzer&gt;) and if the given 
	 * instrumentAnalyzer(InstrumentAnalyzer) isn't already a key there it maps(registers) it under its instrumentSpecification(InstrumentSpecification)
	 * @param ema
	 * @param instrumentSpecification
	 * @param instrumentSpecificationDetails if not null, I passed this from a marketScan so it was already scanned and saved
	 * @param closePositionAndStopAnalyzing - if true, we just want to close this position ands stop analyzing it 
	 */
	public InstrumentAnalyzer(TraderProxy ema,InstrumentSpecification instrumentSpecification, InstrumentSpecificationDetails specificationDetails, boolean closePositionAndStopAnalyzing) {
		this.ema=ema;
		if(specificationDetails!=null){
			this.instrumentSpecification = specificationDetails.getInstrumentSpecification();
			this.instrumentSpecificationDetails=new ArrayList<InstrumentSpecificationDetails>();
			this.instrumentSpecificationDetails.add(specificationDetails);
		}else{
			this.instrumentSpecification = instrumentSpecification;
		}
		boolean isForex=this.instrumentSpecification.getSecurityType()==SecurityType.CASH;
		if(isForex){
			this.analyzerPlugin = new ForexAnalyzerUtils(this);
		}else if("IBM".equals(this.instrumentSpecification.getSymbol().toString()) && this.instrumentSpecification.getSecurityType()==SecurityType.STOCK){
			this.analyzerPlugin = new IBMAnalyzerUtils(this);
		}else{
			this.analyzerPlugin = new AnalyzerUtils(this);
		}
		this.state=new InstrumentAnalyzerState();
		if(closePositionAndStopAnalyzing){
			this.positionService=new ClosePositionService(this);
		}else if(isForex){
			this.positionService=new ForexBracketPositionService(this);
		}else if(this.instrumentSpecification.getSecurityType()==SecurityType.STOCK){
			this.positionService=new StockBracketPositionService(this);
		}else{
			this.positionService=new BracketPositionService(this);
		}
//		this.position=position;
		registerInstrumentAnalyzer(this);
	}
	
	public InstrumentAnalyzer(TraderProxy ema,InstrumentSpecification instrumentSpecification, boolean closePositionAndStopAnalyzing){
		this(ema, instrumentSpecification, null, closePositionAndStopAnalyzing);
	}
	/**
	 * constructor for unregistered InstrumentAnalyzer, just to get access to exchange calendar for loading market scans from the main thread
	 */
	public InstrumentAnalyzer(TraderProxy ema){
		this(ema, null);
	}
	
	/**
	 * constructor for unregistered InstrumentAnalyzer, to get access to exchange calendar and historical data of the given instrument market scans from the main thread
	 */
	public InstrumentAnalyzer(TraderProxy ema, InstrumentSpecification instrumentSpecification){
		this.ema=ema;
		this.state=new InstrumentAnalyzerState();
		this.instrumentSpecification = instrumentSpecification;
		this.analyzerPlugin=null;
	}
	/**
	 * public final static TimeStampFormat fmt = new TimeStampFormat("yyyy/MM/dd HH:mm:ss.SSSnnnnnn");<br/>
	 * {<br/>
	 * formatter.setTimeZone(exchangeCalendar.getTimeZone());<br/>
	 * }
	 */
	public final static TimeStampFormat fmt = new TimeStampFormat("yyyy/MM/dd HH:mm Z");//("yyyy/MM/dd HH:mm:ss.SSS Z");
	{
		//formatter.setTimeZone(exchangeCalendar.getTimeZone());
		//formatter.setTimeZone(TimeZone.getDefault());
		fmt.setTimeZone(NASDAQCalendarSettings.getInstance().getTimeZone());
	}
	/**
	 * private final IEventListener&lt;Quote&gt; quoteFeedListener = new IEventListener&lt;Quote&gt;() {...}
	 */
	private final IEventListener<Quote> quoteFeedListener = new IEventListener<Quote>() {
		public void eventFired(Quote liveQuote) {
			QuoteSeries liveQuoteSeries = liveQuoteSeriesBag.get();
//			if(!RunMe.isRealRun&&liveQuoteSeries.size()!=0){
//				Quote prevQuote=liveQuoteSeries.get(0);
//				assert(prevQuote.getTimeStamp().getNanoseconds()<liveQuote.getTimeStamp().getNanoseconds());
//			}
			
			liveQuoteSeries.add(0, liveQuote);
			if(liveQuoteSeries.size()>CACHE_SIZE){
				QuoteSeries halfCache=liveQuoteSeries.subList(0, CACHE_SIZE/2);
				QuoteSeries quotesToSave =liveQuoteSeries.subList((CACHE_SIZE/2)+1,liveQuoteSeries.size()-1);
				SeriesSpecification seriesSpecification = new SeriesSpecification(instrumentSpecification, 
						quotesToSave.get(quotesToSave.size()-1).getTimeStamp(),
						quotesToSave.get(0).getTimeStamp(), TimeFrame.TIMEFRAME_1_TICK);
				quotesToSave.setSeriesSpecification(seriesSpecification);
				if(RunMe.mode==Mode.LIVE&&useDatabaseForRealTimeQuotes){
					ema.getQuoteDao().update(quotesToSave);
				}
				liveQuoteSeriesBag.set(halfCache);
			}
			//custom
			analyzerPlugin.onQuote(liveQuote);
//			if(!RunMe.isRealRun){//for now do this on back testing only
//				InstrumentAnalyzer.this.getAnalyzerUtils().updateBidAskDifferenceStrength(liveQuote);
//			}
			
			//log.info(liveQuote.toString());
//			if(liveQuoteSeries.size()>1){
//				InstrumentAnalyzer.this.getAnalyzerUtils().updateQuoteBidAskPriceSizeDirection();
//			}
		}
	};
	public static final int CACHE_SIZE=22;//2000;
	
	public static final int NEWS_CACHE_SIZE=4;
	/**
	 * public static final long ONE_SECOND=1000000000L;
	 */
	public static final long ONE_SECOND=1000000000L;//1 second in nanoseconds
	/**
	 * public static final long ONE_MINUTE=60*ONE_SECOND;
	 */
	public static final long ONE_MINUTE=60*ONE_SECOND;//1 min step
    /**
	 * public static final long FIFTEEN_MINUTES=15*ONE_MINUTE;
	 */
	public static final long FIFTEEN_MINUTES=15*ONE_MINUTE;//15 min step
	/**
	 * public static final long ONE_HOUR=60*ONE_MINUTE;
	 */
	public static final long ONE_HOUR=60*ONE_MINUTE;//1 hour step
	
	public static final long ONE_DAY=24*ONE_HOUR;//1 day step
	/**
	 * private final IEventListener&lt;Candle&gt; candleFeedlistener = new IEventListener&lt;Candle&gt;() {...}
	 */
	private final IEventListener<Candle> candleFeedlistener = new IEventListener<Candle>() {
		public void eventFired(Candle liveCandle) {
			CandleSeries liveCandleSeries = liveCandleSeriesBag.get();
//			if(!RunMe.isRealRun&&liveCandleSeries.size()!=0){
//				Candle prevCandle=liveCandleSeries.get(0);
//				assert(prevCandle.getTimeStamp().getNanoseconds()<liveCandle.getTimeStamp().getNanoseconds());
//			}
			analyzerPlugin.updateExtremes(liveCandle);//custom code, here for efficiency
			liveCandleSeries.add(0,liveCandle);
			//log.info(liveCandle.toString());
			if(liveCandleSeries.size()>CACHE_SIZE){
				CandleSeries halfCache=liveCandleSeries.subList(0, CACHE_SIZE/2);
				CandleSeries candlesToSave =liveCandleSeries.subList((CACHE_SIZE/2)+1,liveCandleSeries.size()-1);
				SeriesSpecification seriesSpecification = new SeriesSpecification(instrumentSpecification, 
						candlesToSave.get(candlesToSave.size()-1).getTimeStamp(),
						candlesToSave.get(0).getTimeStamp(), TimeFrame.TIMEFRAME_5_SECONDS);
				candlesToSave.setSeriesSpecification(seriesSpecification);
				if(RunMe.mode==Mode.LIVE&&useDatabaseForRealTimeCandles){
					ema.getCandleDao().update(candlesToSave);
				}
				
				liveCandleSeriesBag.set(halfCache);
			}
			//custom code
//			analyzerPlugin.updateExtremes(liveCandleSeriesBag.get(), liveCandle);
			analyzerPlugin.onCandle(liveCandle);
//			InstrumentAnalyzer.this.getAnalyzerUtils().updateBollingerBands(20, 2, false, liveCandle);
			
//			InstrumentAnalyzer.this.getAnalyzerUtils().updateCandleDirection(liveCandle);
//			InstrumentAnalyzer.this.getAnalyzerUtils().updateCandleVolumeDirection();
//			InstrumentAnalyzer.this.getAnalyzerUtils().updateCandleTradeCountDirection();
//			InstrumentAnalyzer.this.getAnalyzerUtils().updateHammerInfo();
			
//			checkAndOpenPositionIfConditionsAreRight();
		}
	};
	/**
	 * private final IEventListener&lt;TradeIndication&gt; tradeIndicationFeedListener = new IEventListener&lt;TradeIndication&gt;() {...}
	 */
	private final IEventListener<TradeIndication> tradeIndicationFeedListener = new IEventListener<TradeIndication>() {
		public void eventFired(TradeIndication liveTradeIndication) {
			TradeIndicationSeries liveTradeIndicationSeries = liveTradeIndicationSeriesBag.get();
//			if(!RunMe.isRealRun&&liveTradeIndicationSeries.size()!=0){
//				TradeIndication prevTradeIndication=liveTradeIndicationSeries.get(0);
//				assert(prevTradeIndication.getTimeStamp().getNanoseconds()<liveTradeIndication.getTimeStamp().getNanoseconds());
//			}
			liveTradeIndicationSeries.add(0, liveTradeIndication);
			//log.info(liveTradeIndication.toString());			
			if(liveTradeIndicationSeries.size()>CACHE_SIZE){
				TradeIndicationSeries halfCache=liveTradeIndicationSeries.subList(0, CACHE_SIZE/2);
				TradeIndicationSeries tradeIndicationToSave =liveTradeIndicationSeries.subList((CACHE_SIZE/2)+1,liveTradeIndicationSeries.size()-1);
				SeriesSpecification seriesSpecification = new SeriesSpecification(instrumentSpecification, 
						tradeIndicationToSave.get(tradeIndicationToSave.size()-1).getTimeStamp(),
						tradeIndicationToSave.get(0).getTimeStamp(), TimeFrame.TIMEFRAME_1_TICK);
				tradeIndicationToSave.setSeriesSpecification(seriesSpecification);
				if(RunMe.mode==Mode.LIVE&&useDatabaseForRealTimeTradeIndication){
					ema.getTradeIndicationDao().update(tradeIndicationToSave);
				}
				liveTradeIndicationSeriesBag.set(halfCache);
			}
			//custom code
			analyzerPlugin.onTradeIndication(liveTradeIndication);
//			if(!RunMe.isRealRun){//for now do this on back testing only
//				getLog().info("TradeIndication:"+liveTradeIndication.getQuantity()+"@"+liveTradeIndication.getPrice()+"@"+formatter.format(liveTradeIndication.getTimeStamp()));
//			}
//			InstrumentAnalyzer.this.getAnalyzerUtils().updateTradeIndicationData();
//			if(liveTradeIndicationSeries.size()>1){
//				InstrumentAnalyzer.this.getAnalyzerUtils().updateTradeIndicationPriceDirection();
//				InstrumentAnalyzer.this.getAnalyzerUtils().updateTradeIndicationQuantityDirection();
//			}
		}
	};
	
	/**
	 * private final IEventListener&lt;MarketDepthEvent&gt; marketDepthEventFeedListener = new IEventListener&lt;MarketDepthEvent&gt;() {...}
	 */
	private final IEventListener<MarketDepthEvent> marketDepthEventFeedListener = new IEventListener<MarketDepthEvent>() {
		public void eventFired(MarketDepthEvent liveMarketDepthEvent) {

			
			MarketDepthEventSeries liveMarketDepthEventSeries = liveMarketDepthEventSeriesBag.get();
			liveMarketDepthEventSeries.add(0, liveMarketDepthEvent);		
			//log.info(liveMarketDepth.toString());
//			if(liveMarketDepthSeries.size()>1000){
//				liveMarketDepthSeriesBag.set(liveMarketDepthSeries.subList(0, 500));
//			}
			
			if(liveMarketDepthEventSeries.size()>CACHE_SIZE){
				MarketDepthEventSeries halfCache=liveMarketDepthEventSeries.subList(0, CACHE_SIZE/2);
				MarketDepthEventSeries marketDepthEventToSave =liveMarketDepthEventSeries.subList((CACHE_SIZE/2)+1,liveMarketDepthEventSeries.size()-1);
				SeriesSpecification seriesSpecification = new SeriesSpecification(instrumentSpecification, 
						marketDepthEventToSave.get(marketDepthEventToSave.size()-1).getTimeStamp(),
						marketDepthEventToSave.get(0).getTimeStamp(), TimeFrame.TIMEFRAME_1_TICK);
				marketDepthEventToSave.setSeriesSpecification(seriesSpecification);
				if(RunMe.mode==Mode.LIVE&&useDatabaseForRealTimeMarketDepth){
					ema.getMarketDepthEventDao().update(marketDepthEventToSave);
				}
				liveMarketDepthEventSeriesBag.set(halfCache);
			}
			
			
			
//			if(liveMarketDepthSeries.size()!=20)return;
//			
//			MarketDepthRow bestAskmarketDepthRow=liveMarketDepth.getOrderAt(MarketDepth.SIDE_ASK, 0);
//			double askPrice=bestAskmarketDepthRow.getPrice();
//			int askSize=bestAskmarketDepthRow.getSize();
//			Order buyOrder = new Order();
//			buyOrder.setInstrumentSpecification(InstrumentAnalyzer.this.instrumentSpecification);
//			buyOrder.setOrderTimeStamp(new TimeStamp());
//			buyOrder.setOrderSide(OrderSide.BUY);
//			buyOrder.setOrderType(OrderType.LIMIT);
//			buyOrder.setQuantity(askSize*5);
//			buyOrder.setLimitPrice(askPrice);
//			InstrumentAnalyzer.this.getPositionService().openPosition(buyOrder);
			
		}
	};
	/**
	 * private final IEventListener&lt;TickerNews&gt; tickerNewsFeedListener = new IEventListener&lt;TickerNews&gt;() {...}
	 */
	private final IEventListener<TickerNews> tickerNewsFeedListener = new IEventListener<TickerNews>() {
		public void eventFired(TickerNews liveTickerNews) {
			TickerNewsSeries liveTickerNewsSeries = liveTickerNewsSeriesBag.get();
//			if(!RunMe.isRealRun&&liveTickerNewsSeries.size()!=0){
//				TradeIndication prevTickerNews=liveTickerNewsSeries.get(0);
//				assert(prevTickerNews.getTimeStamp().getNanoseconds()<liveTickerNews.getTimeStamp().getNanoseconds());
//			}
			liveTickerNewsSeries.add(0, liveTickerNews);
				
			if(liveTickerNewsSeries.size()>NEWS_CACHE_SIZE){
				TickerNewsSeries halfCache=liveTickerNewsSeries.subList(0, NEWS_CACHE_SIZE/2);
//				TickerNewsSeries tickerNewsToSave =liveTickerNewsSeries.subList((NEWS_CACHE_SIZE/2)+1,liveTickerNewsSeries.size()-1);
//				SeriesSpecification seriesSpecification = new SeriesSpecification(instrumentSpecification, 
//						tickerNewsToSave.get(tickerNewsToSave.size()-1).getTimeStamp(),
//						tickerNewsToSave.get(0).getTimeStamp(), TimeFrame.TIMEFRAME_1_TICK);
//				tickerNewsToSave.setSeriesSpecification(seriesSpecification);
//				if(RunMe.isRealRun&&useDatabaseForRealTimeTickerNews){
//					ema.getTickerNewsDao().update(tickerNewsToSave);
//				}
				liveTickerNewsSeriesBag.set(halfCache);
			}
			if(RunMe.mode==Mode.LIVE&&useDatabaseForRealTimeTickerNews){
//				TickerNewsSeries tickerNewsToSave =liveTickerNewsSeries.subList(0,1);//save the latest
				ema.getTickerNewsDao().update(liveTickerNews);
			}
			
			
			//custom stuff starts here
			TimeStamp liveTickerNewsTimeStamp = liveTickerNews.getTimeStamp();
			//hack
//			liveTickerNewsTimeStamp=new TimeStamp(liveTickerNewsTimeStamp.getNanoseconds()-(24*DURATION_60_MINUTES));
//			liveTickerNews.setTimeStamp(liveTickerNewsTimeStamp);
			//hack end
			log.info(getInstrumentSpecification().getSymbol().toString()+ " news@"+liveTickerNews.getTimeStamp().getDate()+": "+liveTickerNews.getTitle());	
			analyzerPlugin.onTickerNews(liveTickerNews);
			
			
			
			TimeStamp ONE_HOUR_LATER=new TimeStamp(liveTickerNewsTimeStamp.getNanoseconds()+ONE_HOUR);
			
			CandleSeries candleSeries = getT_MINUS_5D_HistoryCandles().getTimeFrame(liveTickerNewsTimeStamp, ONE_HOUR_LATER);
			if(candleSeries.size()!=0){//do I have any data for this
			Candle high = candleSeries.getHigh();
			Candle low = candleSeries.getLow();
			
			Candle candleJustBeforeNews=candleSeries.firstElement();
			
			double priceBeforeNews=candleJustBeforeNews.getOpenPrice();
			
			double highPrice=high.getHighPrice();
			TimeStamp highTimeStamp=high.getHighTimeStamp();
			
			double lowPrice=low.getLowPrice();
			TimeStamp lowTimeStamp=low.getLowTimeStamp();
			
			double priceChange=100*(highPrice-lowPrice)/priceBeforeNews;
			
			long diffMin=(lowTimeStamp.getNanoseconds()-highTimeStamp.getNanoseconds())/(ONE_MINUTE);
			log.info("News was received at "+fmt.format(liveTickerNewsTimeStamp)+" Price moved "+priceChange+"%"+ ((diffMin>0)?"up ":"down ")+ 
			((lowTimeStamp.isBefore(highTimeStamp))?(fmt.format(lowTimeStamp)+"-"+fmt.format(highTimeStamp)):(fmt.format(highTimeStamp)+"-"+fmt.format(lowTimeStamp)) ));
			}
			
		}
	};
	/**
	 * Looks at the associated instrumentAnalyzers(Map&lt;InstrumentSpecification,InstrumentAnalyzer&gt;) and if the given 
	 * instrumentAnalyzer(InstrumentAnalyzer) isn't already a key there it maps it under its instrumentSpecification(InstrumentSpecification)
	 * @param instrumentAnalyzer
	 */
	static void registerInstrumentAnalyzer(InstrumentAnalyzer instrumentAnalyzer) {
		InstrumentSpecification spec = instrumentAnalyzer.instrumentSpecification;
		StringBuffer title=new StringBuffer(spec.getSymbol().toString());
		if(spec.getSecurityType()==SecurityType.CASH){
			title.append("/");
			title.append(spec.getCurrency().toString());
		}
		if(instrumentAnalyzers.get(spec)==null){
			instrumentAnalyzers.put(spec, instrumentAnalyzer);
			if(spec.getSecurityType()==SecurityType.CASH){
				CurrencyPairs.mapForexAnalyzer(instrumentAnalyzer);
			}	
			log.info("registered instrument:"+title+" Total instruments #:"+instrumentAnalyzers.size());			
		}else{
			log.info("already looking at instrument:"+title);
		}
	}
	/**
	 * returns T-5 exchangeTradingHours(Map&lt;TimeStamp,Interval&lt;TimeStamp&gt;&gt;) holding the T-5 trading hour intervals for the given exchangeCalendar(ExchangeCalendar)
	 * @param exchangeCalendar
	 * @return
	 */
	public Map<TimeStamp,Interval<TimeStamp>> getExchangeTradingHours_T_MINUS_5(IExchangeCalendar exchangeCalendar){
		Map<TimeStamp,Interval<TimeStamp>> hours = exchangeTradingHours_T_MINUS_5.get(exchangeCalendar);
		if(hours==null){
			hours=getExchangeTradingHours(exchangeCalendar, 5);
			exchangeTradingHours_T_MINUS_5.put(exchangeCalendar, hours);
		}
		return hours;
	}	
	/**
	 * returns T-5 exchangeTradingRange(Interval&lt;TimeStamp&gt;) holding the T-5 trading range for the given exchangeCalendar(IExchangeCalendar)
	 * @param exchangeCalendar
	 * @return
	 */
	public Interval<TimeStamp> getExchangeTradingRange_T_MINUS_5(IExchangeCalendar exchangeCalendar){
		Interval<TimeStamp> ranges = exchangeTradingRange_T_MINUS_5.get(exchangeCalendar);
		if(ranges==null){
			ranges=getExchangeTradingRanges(exchangeCalendar, 5);
			exchangeTradingRange_T_MINUS_5.put(exchangeCalendar, ranges);
		}
		return ranges;
	}
	/**
	 * returns T-5 exchangeTradingRange(Interval&lt;TimeStamp&gt;) holding the T-30 trading range for the given exchangeCalendar(IExchangeCalendar)
	 * @param exchangeCalendar
	 * @return
	 */
	public Interval<TimeStamp> getExchangeTradingRange_T_MINUS_30(IExchangeCalendar exchangeCalendar){
		Interval<TimeStamp> ranges = exchangeTradingRange_T_MINUS_30.get(exchangeCalendar);
		if(ranges==null){
			ranges=getExchangeTradingRanges(exchangeCalendar, 30);
			exchangeTradingRange_T_MINUS_30.put(exchangeCalendar, ranges);
		}
		return ranges;
	}
	/**
	 * returns T-1Y exchangeTradingRange(Interval&lt;TimeStamp&gt;) holding the T-1Y trading range for the given exchangeCalendar(IExchangeCalendar).<br/>
	 * The end of the interval is at the next opening time for the given exchange (I do this to make sure it doesn't go beyond 1 year)
	 * @param exchangeCalendar
	 * @return
	 */
	public Interval<TimeStamp> getExchangeTradingRange_T_MINUS_1Y(IExchangeCalendar exchangeCalendar){
		Interval<TimeStamp> ranges = exchangeTradingRange_T_MINUS_1Y.get(exchangeCalendar);
		if(ranges==null){
			ranges=getExchangeTradingRangesT_MINUS_1Y(exchangeCalendar);
			exchangeTradingRange_T_MINUS_1Y.put(exchangeCalendar, ranges);
		}
		return ranges;
	}
	
	public Map<TimeStamp,Interval<TimeStamp>> getExchangeTradingHours(IExchangeCalendar exchangeCalendar, int businessDays) {
		Interval<TimeStamp> interval = getExchangeTradingRanges(exchangeCalendar, businessDays);
		Map<TimeStamp,Interval<TimeStamp>> tradingHours = exchangeCalendar.getTradingHours(interval.from, interval.to);
		return tradingHours;
	}
	/**
	 * Returns an Interval&gt;TimeStamp&gt; whose from-to mark a T-businessDays period for the given exchangeCalendar(IExchangeCalendar)
	 * ending with the current time or the last closing time(if the exchange is closed right now)
	 * @param exchangeCalendar
	 * @param businessDays
	 * @return
	 */
	public Interval<TimeStamp> getExchangeTradingRanges(IExchangeCalendar exchangeCalendar, int businessDays) {
		TimeStamp now = ema.getTimeStampService().currentTimeStamp();
		TimeStamp to = null;
//		log.info("now is "+fmt.format(now));
		TimeStamp openingTimeToday = exchangeCalendar.getOpeningTimeTodayAsTimeStamp(now);
		if(exchangeCalendar.isOpen(now)&&openingTimeToday!=null&&now.isAfter(openingTimeToday)){
			to= now;
		}else{
			to = exchangeCalendar.getLastClosingAsTimeStamp(now);
		}		
		TimeStamp from = new TimeStamp(to.getNanoseconds());		
		int days=0;
		Calendar calendar;
		while(days<businessDays){
			from=addDays(from, -1);
			calendar=GregorianCalendar.getInstance(exchangeCalendar.getTimeZone());
			calendar.setTimeInMillis(from.getDate().getTime());
			if(exchangeCalendar.getHolidayCalendar().isOnSameDay(from)||!isBusinessDay(calendar)){
				continue;
			}
			days++;
		}
		from = exchangeCalendar.getNextOpeningAsTimeStamp(from);
//		log.info("Now:" + fmt.format(now));
//		log.info("Trading Range:" + fmt.format(from) + " end:" + fmt.format(to));
		return new Interval<TimeStamp>(from, to);		
	}
	/**
	 * Returns an Interval&lt;TimeStamp&gt; whose from-to pair starts a year ago and ends at the next opening time for the given exchangeCalendar(IExchangeCalendar)
	 * @param exchangeCalendar
	 * @return
	 */
	public Interval<TimeStamp> getExchangeTradingRangesT_MINUS_1Y(IExchangeCalendar exchangeCalendar) {
		TimeStamp now = ema.getTimeStampService().currentTimeStamp();
		TimeStamp to = exchangeCalendar.getNextOpeningAsTimeStamp(now);
		//to=new TimeStamp(to.getNanoseconds()+(IBTwsConnection.DURATION_DAY*1000000000L));//had to add 1 day after changing to EST
		Calendar calendar=GregorianCalendar.getInstance(exchangeCalendar.getTimeZone());
		calendar.setTime(to.getDate());
		calendar.add(Calendar.YEAR, -1);
		calendar.add(Calendar.DAY_OF_YEAR, 1);
		TimeStamp from = new TimeStamp(calendar.getTime(), 0);
		from = exchangeCalendar.getNextOpeningAsTimeStamp(from);
		log.info("1Y range: "+fmt.format(from)+" "+fmt.format(to));
		return new Interval<TimeStamp>(from, to);		
	}
	/**
	 * returns CandleSeries holding last 5 days 1 Minute historical Candles (either from the in-memory cache, or the database or IB).<br/>
	 * If it gets them from IB it'll store them in the database for next time. IT also puts them into the associated historicalCandleSeriesCache(Map&lt;SeriesSpecification,CandleSeries&gt;)
	 * @return
	 */
	public CandleSeries getT_MINUS_5D_HistoryCandles() {
		Interval<TimeStamp> T_MINUS_5D_INTERVAL = getExchangeTradingRange_T_MINUS_5(exchangeCalendar);
		TimeStamp from = T_MINUS_5D_INTERVAL.from;
		TimeStamp to = T_MINUS_5D_INTERVAL.to;
//		TimeStamp to = RunMe.isRealRun?T_MINUS_5D_INTERVAL.to:new TimeStamp();//for backtesting just load everything you got up till now
		SeriesSpecification spec = new SeriesSpecification(instrumentSpecification, from, to, TimeFrame.TIMEFRAME_1_MINUTE);
		CandleSeries candleSeries=historicalCandleSeriesCache.get(spec);
		boolean success=false;
		if(useDatabaseFor5DHistoricData&&candleSeries==null){
			Candle[] candles=ema.getCandleDao().findBySeriesSpecification(spec);
			success=candles.length > 30;
			if(candles.length!=0){//some candles are already there
				Candle latestCandleInDatabase=candles[0];
				TimeStamp latestCandleInDatabaseTimeStamp=latestCandleInDatabase.getTimeStamp();
				if(to.isAfter(latestCandleInDatabaseTimeStamp)){
					long totalTradingSecondsMissing = SeriesSpecHelper.getTotalTradingSecondsInBetween(latestCandleInDatabaseTimeStamp, to, exchangeCalendar);
					if(totalTradingSecondsMissing>8*3600){//if I am missing more than the last 8 trading hours, lets fetch those
						TimeStamp partialFetchFrom = new TimeStamp(latestCandleInDatabaseTimeStamp.getNanoseconds()+1L);//60sec
						SeriesSpecification partialFetchSpec = new SeriesSpecification(instrumentSpecification, partialFetchFrom, to, TimeFrame.TIMEFRAME_1_MINUTE);
						CandleSeries partialCandleSeries=null;
						state.standInLineForCandleT_MINUS_5DHistoryRequest();
						ema.getIbConnection().setOptimizeFetchGranularity(true);
						try {
							partialCandleSeries = ema.getCandleSeriesSource().fetch(partialFetchSpec);
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						if(partialCandleSeries != null){
							partialCandleSeries=partialCandleSeries.getTimeFrame(partialFetchFrom, to);
						}
						success=partialCandleSeries != null && partialCandleSeries.size() != 0;
						if(success){
							log.info("[getT_MINUS_5D_HistoryCandles] here "+latestCandleInDatabaseTimeStamp+ " "+partialFetchFrom);
							ema.getCandleDao().update(partialCandleSeries);
						}
						candleSeries = new CandleSeries(spec, candles);
						candleSeries = TimeSeriesUtils.merge(candleSeries, partialCandleSeries);
						historicalCandleSeriesCache.put(spec, candleSeries);
						state.setCandleT_MINUS_5DHistoryRequestReceived(success);		
						return candleSeries;
					}
				}
			}
			if (success) {
				candleSeries = new CandleSeries(spec, candles);
				historicalCandleSeriesCache.put(spec, candleSeries);
				state.setCandleT_MINUS_5DHistoryLoadedFromDatabase();
			}
		}
		if (candleSeries == null) {
			state.standInLineForCandleT_MINUS_5DHistoryRequest();
			ema.getIbConnection().setOptimizeFetchGranularity(true);
			CandleSeries candleSeriesIn5D = null;
			
			try {				
				candleSeries = ema.getCandleSeriesSource().fetch(spec);
				log.info("[getT_MINUS_5D_HistoryCandles] spec: "+spec.getStartTimeStamp()+" to "+spec.getEndTimeStamp()+" fetched:"+candleSeries.get(candleSeries.size()-1).getTimeStamp()+" to "+candleSeries.get(0).getTimeStamp());
				//get and store separately the Candles who belong in the T_MINUS_5D interval
				candleSeriesIn5D = candleSeries.getTimeFrame(spec.getStartTimeStamp(), candleSeries.get(0).getTimeStamp());				
//				log.info("[getT_MINUS_5D_HistoryCandles] spec after trim: "+spec.getStartTimeStamp()+" to "+spec.getEndTimeStamp()+" fetched:"+candleSeries.get(candleSeries.size()-1).getTimeStamp()+" to "+candleSeries.get(0).getTimeStamp());
				historicalCandleSeriesCache.put(spec, candleSeries);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			success=candleSeries != null && candleSeries.size() > 30;
			if(useDatabaseFor5DHistoricData&&success){
//				log.info("[getT_MINUS_5D_HistoryCandles] here1 "+candleSeries.size());
				ema.getCandleDao().update(candleSeriesIn5D);
				CandleSeries candleSeriesBefore5D = null;
				boolean blewUp=false;
				TimeStamp checkpoint=new TimeStamp(spec.getStartTimeStamp().getNanoseconds()-1);//checkpoint starts just before start
				candleSeriesBefore5D = candleSeries.getTimeFrame(new TimeStamp(checkpoint.getNanoseconds()-ONE_HOUR), checkpoint);//backs up 1 hours each time
				log.info("[getT_MINUS_5D_HistoryCandles] spec3: "+new TimeStamp(checkpoint.getNanoseconds()-ONE_HOUR)+" to "+checkpoint+" fetched:"+candleSeriesBefore5D.size()+"candles");
				while(!blewUp&&candleSeriesBefore5D.size()!=0){
					try{
						ema.getCandleDao().update(candleSeriesBefore5D);
						checkpoint=new TimeStamp(checkpoint.getNanoseconds()-(ONE_HOUR+1));
						candleSeriesBefore5D = candleSeries.getTimeFrame(new TimeStamp(checkpoint.getNanoseconds()-ONE_HOUR), checkpoint);//backs up 2 hours each time
						log.info("[getT_MINUS_5D_HistoryCandles] spec4: "+new TimeStamp(checkpoint.getNanoseconds()-ONE_HOUR)+" to "+checkpoint+" fetched:"+candleSeriesBefore5D.size()+"candles");
					}catch(Exception e){
						log.warn("[getT_MINUS_5D_HistoryCandles] I blew up", e);
						blewUp=true;
					}					
				}
				
			}
			state.setCandleT_MINUS_5DHistoryRequestReceived(candleSeries != null && candleSeries.size() > 100);											
		}
		return candleSeries;
	}
	/**
	 * returns CandleSeries holding last 30 days 1 Minute historical Candles (either from the in-memory cache, or the database or IB).<br/>
	 * If it gets them from IB it'll store them in the database for next time. IT also puts them into the associated historicalCandleSeriesCache(Map&lt;SeriesSpecification,CandleSeries&gt;)
	 * @return
	 */
	public CandleSeries getT_MINUS_30D_HistoryCandles() {
		Interval<TimeStamp> T_MINUS_30D_INTERVAL = getExchangeTradingRange_T_MINUS_30(exchangeCalendar);
		TimeStamp from = T_MINUS_30D_INTERVAL.from;
		TimeStamp to = T_MINUS_30D_INTERVAL.to;
//		TimeStamp to = RunMe.isRealRun?T_MINUS_30D_INTERVAL.to:new TimeStamp();//for backtesting just load everything you got up till now
		SeriesSpecification spec = new SeriesSpecification(instrumentSpecification, from, to, TimeFrame.TIMEFRAME_1_MINUTE);
		CandleSeries candleSeries=historicalCandleSeriesCache.get(spec);
		boolean success=false;
		if(useDatabaseFor30DHistoricData&&candleSeries==null){
			Candle[] candles=ema.getCandleDao().findBySeriesSpecification(spec);
			success=candles.length > 30;
			if(candles.length!=0){//some candles are already there
				Candle latestCandleInDatabase=candles[0];
				TimeStamp latestCandleInDatabaseTimeStamp=latestCandleInDatabase.getTimeStamp();
				if(to.isAfter(latestCandleInDatabaseTimeStamp)){
					long totalTradingSecondsMissing = SeriesSpecHelper.getTotalTradingSecondsInBetween(latestCandleInDatabaseTimeStamp, to, exchangeCalendar);
					if(totalTradingSecondsMissing>8*3600){//if I am missing more than the last 8 trading hours, lets fetch those
						TimeStamp partialFetchFrom = new TimeStamp(latestCandleInDatabaseTimeStamp.getNanoseconds()+1L);//60sec
						SeriesSpecification partialFetchSpec = new SeriesSpecification(instrumentSpecification, partialFetchFrom, to, TimeFrame.TIMEFRAME_1_MINUTE);
						CandleSeries partialCandleSeries=null;
						state.standInLineForCandleT_MINUS_5DHistoryRequest();
						ema.getIbConnection().setOptimizeFetchGranularity(true);
						try {
							partialCandleSeries = ema.getCandleSeriesSource().fetch(partialFetchSpec);
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						if(partialCandleSeries != null){
							partialCandleSeries=partialCandleSeries.getTimeFrame(partialFetchFrom, to);
						}
						success=partialCandleSeries != null && partialCandleSeries.size() != 0;
						if(success){
							log.info("[getT_MINUS_30D_HistoryCandles] here "+latestCandleInDatabaseTimeStamp+ " "+partialFetchFrom);
							ema.getCandleDao().update(partialCandleSeries);
						}
						candleSeries = new CandleSeries(spec, candles);
						candleSeries = TimeSeriesUtils.merge(candleSeries, partialCandleSeries);
						historicalCandleSeriesCache.put(spec, candleSeries);
						state.setCandleT_MINUS_5DHistoryRequestReceived(success);		
						return candleSeries;
					}
				}
			}
			if (success) {
				candleSeries = new CandleSeries(spec, candles);
				historicalCandleSeriesCache.put(spec, candleSeries);
				state.setCandleT_MINUS_5DHistoryLoadedFromDatabase();
			}
		}
		if (candleSeries == null) {
			state.standInLineForCandleT_MINUS_5DHistoryRequest();
			ema.getIbConnection().setOptimizeFetchGranularity(true);
			CandleSeries candleSeriesIn30D = null;
			try {				
				candleSeries = ema.getCandleSeriesSource().fetch(spec);
				log.info("[getT_MINUS_30D_HistoryCandles] spec: "+spec.getStartTimeStamp()+" to "+spec.getEndTimeStamp()+" fetched:"+candleSeries.get(candleSeries.size()-1).getTimeStamp()+" to "+candleSeries.get(0).getTimeStamp());
				//get and store separately the Candles who belong in the T_MINUS_5D interval
				candleSeriesIn30D = candleSeries.getTimeFrame(spec.getStartTimeStamp(), candleSeries.get(0).getTimeStamp());				
//				log.info("[getT_MINUS_30D_HistoryCandles] spec after trim: "+spec.getStartTimeStamp()+" to "+spec.getEndTimeStamp()+" fetched:"+candleSeries.get(candleSeries.size()-1).getTimeStamp()+" to "+candleSeries.get(0).getTimeStamp());
				historicalCandleSeriesCache.put(spec, candleSeries);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			success=candleSeries != null && candleSeries.size() > 30;
			if(useDatabaseFor30DHistoricData&&success){
//				log.info("[getT_MINUS_5D_HistoryCandles] here1 "+candleSeries.size());
				ema.getCandleDao().update(candleSeriesIn30D);
				CandleSeries candleSeriesBefore30D = null;
				boolean blewUp=false;
				TimeStamp checkpoint=new TimeStamp(spec.getStartTimeStamp().getNanoseconds()-1);//checkpoint starts just before start
				candleSeriesBefore30D = candleSeries.getTimeFrame(new TimeStamp(checkpoint.getNanoseconds()-ONE_HOUR), checkpoint);//backs up 1 hours each time
				log.info("[getT_MINUS_30D_HistoryCandles] spec: "+new TimeStamp(checkpoint.getNanoseconds()-ONE_HOUR)+" to "+checkpoint+" fetched:"+candleSeriesBefore30D.size()+"candles");
				while(!blewUp&&candleSeriesBefore30D.size()!=0){
					try{
						ema.getCandleDao().update(candleSeriesBefore30D);
						checkpoint=new TimeStamp(checkpoint.getNanoseconds()-(ONE_HOUR+1));
						candleSeriesBefore30D = candleSeries.getTimeFrame(new TimeStamp(checkpoint.getNanoseconds()-ONE_HOUR), checkpoint);//backs up 2 hours each time
						log.info("[getT_MINUS_30D_HistoryCandles] spec: "+new TimeStamp(checkpoint.getNanoseconds()-ONE_HOUR)+" to "+checkpoint+" fetched:"+candleSeriesBefore30D.size()+"candles");
					}catch(Exception e){
						log.warn("[getT_MINUS_30D_HistoryCandles] I blew up", e);
						blewUp=true;
					}					
				}
			}
			state.setCandleT_MINUS_5DHistoryRequestReceived(candleSeries != null && candleSeries.size() > 100);											
		}
		return candleSeries;
	}
	
	
	public QuoteSeries getT_MINUS_5D_HistoryQuotes() {
		Interval<TimeStamp> T_MINUS_5D_INTERVAL = getExchangeTradingRange_T_MINUS_5(exchangeCalendar);
		TimeStamp from = T_MINUS_5D_INTERVAL.from;
		TimeStamp to = T_MINUS_5D_INTERVAL.to;
		SeriesSpecification spec = new SeriesSpecification(instrumentSpecification, from, to, TimeFrame.TIMEFRAME_1_MINUTE);
		QuoteSeries quoteSeries=historicalQuoteSeriesCache.get(spec);
		if(quoteSeries==null){
				ema.getIbConnection().setOptimizeFetchGranularity(true);
			try {
				quoteSeries = ema.getQuoteSeriesSource().fetch(spec);
				historicalQuoteSeriesCache.put(spec, quoteSeries);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return quoteSeries;
	}
		
	public MarketScan[] getT_MINUS_5D_HistoryMarketScans(ScanCriteria scanCriteria) {
		Interval<TimeStamp> T_MINUS_5D_INTERVAL = getExchangeTradingRange_T_MINUS_5(exchangeCalendar);		
		TimeStamp from = T_MINUS_5D_INTERVAL.from;
		TimeStamp to = T_MINUS_5D_INTERVAL.to;				
		MarketScanSeriesSpecification spec = new MarketScanSeriesSpecification(scanCriteria, from, to, TimeFrame.TIMEFRAME_1_TICK);
		MarketScanSeries marketScanSeries=historicalMarketScanCache.get(spec);
		MarketScan[] marketScans=null;
		if(marketScanSeries==null){
			marketScans=ema.getMarketScanDao().findByMarketScanSeriesSpecification(spec);
			marketScanSeries = new MarketScanSeries(spec, marketScans);
			historicalMarketScanCache.put(spec, marketScanSeries);
		}
		return marketScans;
	}
	
	public MarketScan[] getT_MINUS_30D_HistoryMarketScans(ScanCriteria scanCriteria) {
		Interval<TimeStamp> T_MINUS_30D_INTERVAL = getExchangeTradingRange_T_MINUS_30(exchangeCalendar);		
		TimeStamp from = T_MINUS_30D_INTERVAL.from;
		TimeStamp to = T_MINUS_30D_INTERVAL.to;				
		MarketScanSeriesSpecification spec = new MarketScanSeriesSpecification(scanCriteria, from, to, TimeFrame.TIMEFRAME_1_TICK);
		MarketScanSeries marketScanSeries=historicalMarketScanCache.get(spec);
		MarketScan[] marketScans=null;
		if(marketScanSeries==null){
			marketScans=ema.getMarketScanDao().findByMarketScanSeriesSpecification(spec);
			marketScanSeries = new MarketScanSeries(spec, marketScans);
			historicalMarketScanCache.put(spec, marketScanSeries);
		}
		return marketScans;
	}
	
	public MarketScan[] getT_MINUS_30D_HistoryMarketScansForThisInstrument(ScanCriteria scanCriteria) {
		Interval<TimeStamp> T_MINUS_30D_INTERVAL = getExchangeTradingRange_T_MINUS_30(exchangeCalendar);		
		TimeStamp from = T_MINUS_30D_INTERVAL.from;
		TimeStamp to = T_MINUS_30D_INTERVAL.to;				
		MarketScanSeriesSpecification spec = new MarketScanSeriesSpecification(scanCriteria, from, to, TimeFrame.TIMEFRAME_1_TICK);
		MarketScanSeries marketScanSeries=historicalMarketScanCacheForThisInstrument.get(spec);
		MarketScan[] marketScans=null;
		if(marketScanSeries==null){
			marketScans=ema.getMarketScanDao().findByMarketScanSeriesSpecificationAndInstrumentSpecification(spec, instrumentSpecification);
			marketScanSeries = new MarketScanSeries(spec, marketScans);
			historicalMarketScanCacheForThisInstrument.put(spec, marketScanSeries);
		}
		return marketScans;
	}
	
	/**
	 * Returns T-1Y 1DAY Candles (cached if I already have them or by waiting turn and fetching from IB) and then
	 * puts caches them in the associated historicalCandleSeriesCache(Map&lt;SeriesSpecification,CandleSeries&gt;) for reuse.
	 * @return
	 */
	public CandleSeries getT_MINUS_1Y_HistoryCandles() {			
		Interval<TimeStamp> T_MINUS_1Y_INTERVAL = getExchangeTradingRange_T_MINUS_1Y(exchangeCalendar);
		TimeStamp from = T_MINUS_1Y_INTERVAL.from;
		TimeStamp to = T_MINUS_1Y_INTERVAL.to;
		SeriesSpecification spec = new SeriesSpecification(instrumentSpecification, from, to, TimeFrame.TIMEFRAME_1_DAY);
		CandleSeries candleSeries=historicalCandleSeriesCache.get(spec);
		boolean success=false;
		if(useDatabaseFor1YHistoricData&&candleSeries==null){
			Candle[] candles=ema.getCandleDao().findBySeriesSpecification(spec);
			//candleSeries.
			success=candles.length > 30;
			if (success) {
				candleSeries = new CandleSeries(spec, candles);
				historicalCandleSeriesCache.put(spec, candleSeries);
				state.setCandleT_MINUS_1YHistoryLoadedFromDatabase();
			}
		}
		if(candleSeries==null){
			state.standInLineForCandleT_MINUS_1YHistoryRequest();
			ema.getIbConnection().setOptimizeFetchGranularity(true);
			try {
				candleSeries = ema.getCandleSeriesSource().fetch(spec);
				historicalCandleSeriesCache.put(spec, candleSeries);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			success=candleSeries != null && candleSeries.size() > 30;
			if(useDatabaseFor1YHistoricData&&success){
				ema.getCandleDao().update(candleSeries);
			}
			state.setCandleT_MINUS_1YHistoryRequestReceived(success);//usually we will have 251 Candles here
		}
		
		return candleSeries;
	}

	public TickerNewsSeries getT_MINUS_5D_HistoryTickerNews() {		
		Interval<TimeStamp> T_MINUS_5D_INTERVAL = getExchangeTradingRange_T_MINUS_5(exchangeCalendar);
		TimeStamp from = T_MINUS_5D_INTERVAL.from;
		TimeStamp to = T_MINUS_5D_INTERVAL.to;
		SeriesSpecification spec = new SeriesSpecification(instrumentSpecification, from, to, TimeFrame.TIMEFRAME_1_TICK);
		TickerNewsSeries tickerNewsSeries=historicalTickerNewsSeriesCache.get(spec);
		boolean success=false;
		if(useDatabaseFor5DHistoricData&&tickerNewsSeries==null){
			TickerNews[] tickerNews=ema.getTickerNewsDao().findBySeriesSpecification(spec);
			//candleSeries.
			success=tickerNews.length > 2;
			if (success) {
				tickerNewsSeries = new TickerNewsSeries(spec, tickerNews);
				historicalTickerNewsSeriesCache.put(spec, tickerNewsSeries);
				state.setTickerNewsT_MINUS_5DHistoryLoadedFromDatabase();
			}
		}
		if (tickerNewsSeries == null) {
			state.standInLineForTickerNewsT_MINUS_5DHistoryRequest();
			try {
				tickerNewsSeries = ema.getTickerNewsSeriesSource().fetch(spec);
				historicalTickerNewsSeriesCache.put(spec, tickerNewsSeries);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			success=tickerNewsSeries != null && tickerNewsSeries.size() > 2;
			if(useDatabaseFor5DHistoricData&&success){
				ema.getTickerNewsDao().update(tickerNewsSeries);
			}
			state.setTickerNewsT_MINUS_5DHistoryRequestReceived(tickerNewsSeries != null && tickerNewsSeries.size() > 2);										
		}
		return tickerNewsSeries;
	}
	
	public TickerNewsSeries getT_MINUS_30D_HistoryTickerNews() {
		Interval<TimeStamp> T_MINUS_30D_INTERVAL = getExchangeTradingRange_T_MINUS_30(exchangeCalendar);		
		TimeStamp from = T_MINUS_30D_INTERVAL.from;
		TimeStamp to = T_MINUS_30D_INTERVAL.to;				
		SeriesSpecification spec = new SeriesSpecification(instrumentSpecification, from, to, TimeFrame.TIMEFRAME_1_TICK);
		TickerNewsSeries tickerNewsSeries=historicalTickerNewsSeriesCache.get(spec);
		TickerNews[] tickerNews=null;
		if(tickerNewsSeries==null){
			tickerNews=ema.getTickerNewsDao().findBySeriesSpecification(spec);
			tickerNewsSeries = new TickerNewsSeries(spec, tickerNews);
			historicalTickerNewsSeriesCache.put(spec, tickerNewsSeries);
		}
		return tickerNewsSeries;
	}
	
	public void subscribeToLive5SecondCandleFeed() {
		try {
			liveCandleSubscription=ema.getCandleSubscriptionSource().subscribe(instrumentSpecification, TimeFrame.TIMEFRAME_5_SECONDS);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if(liveCandleSeriesBag.get()==null){
			CandleSeries series=new CandleSeries();
			liveCandleSeriesBag.set(series);
		}
		liveCandleSubscription.addEventListener(candleFeedlistener);
		liveCandleSubscription.activate();
		state.liveCandleFeedActivated();
	}
	
	public void cancelLive5SecondCandleFeed() {
		if (liveCandleSubscription != null) {
			liveCandleSubscription.removeEventListener(candleFeedlistener);
			liveCandleSubscription.cancel();
		}
		state.liveCandleFeedDeactivated();
	}
	
	public void subscribeToLiveQuoteFeed() {
		try {
			liveQuoteSubscription=ema.getQuoteSubscriptionSource().subscribe(instrumentSpecification);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if(liveQuoteSeriesBag.get()==null){
			QuoteSeries series=new QuoteSeries();
			liveQuoteSeriesBag.set(series);
		}
		liveQuoteSubscription.addEventListener(quoteFeedListener);
		liveQuoteSubscription.activate();
		state.liveQuoteFeedActivated();
	}

	public void cancelLiveQuoteFeed() {
		if (liveQuoteSubscription != null) {
			liveQuoteSubscription.removeEventListener(quoteFeedListener);
			liveQuoteSubscription.cancel();
		}
		state.liveQuoteFeedDeactivated();
	}
	
	public void subscribeToLiveTradeIndicationFeed() {
		try {
			liveTradeIndicationSubscription=ema.getTradeIndicationSubscriptionSource().subscribe(instrumentSpecification);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if(liveTradeIndicationSeriesBag.get()==null){
			TradeIndicationSeries series=new TradeIndicationSeries();
			liveTradeIndicationSeriesBag.set(series);
		}
		liveTradeIndicationSubscription.addEventListener(tradeIndicationFeedListener);
		liveTradeIndicationSubscription.activate();
		state.liveTradeIndicationFeedActivated();
	}
	
	public void cancelLiveTradeIndicationFeed() {
		if (liveTradeIndicationSubscription != null) {
			liveTradeIndicationSubscription.removeEventListener(tradeIndicationFeedListener);
			liveTradeIndicationSubscription.cancel();
		}
		state.liveTradeIndicationFeedDeactivated();	
	}
	
	public void subscribeToLiveMarketDepthFeed() {
		state.makeRoomForMarketDepthFeed(this.instrumentSpecification);
		try {
			liveMarketDepthEventSubscription=ema.getMarketDepthEventSubscriptionSource().subscribe(instrumentSpecification);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if(liveMarketDepthEventSeriesBag.get()==null){
			MarketDepthEventSeries series=new MarketDepthEventSeries();
			liveMarketDepthEventSeriesBag.set(series);
		}
		liveMarketDepthEventSubscription.addEventListener(marketDepthEventFeedListener);
		liveMarketDepthEventSubscription.activate();
		state.liveMarketDepthFeedActivated();
	}
	
	public void cancelLiveMarketDepthFeed() {
		if (liveMarketDepthEventSubscription != null) {
			liveMarketDepthEventSubscription.removeEventListener(marketDepthEventFeedListener);
			liveMarketDepthEventSubscription.cancel();
		}
		state.liveMarketDepthFeedDeactivated();	
	}
	
	public void subscribeToLiveTickerNewsFeed() {
		try {
			liveTickerNewsSubscription=ema.getTickerNewsSubscriptionSource().subscribe(instrumentSpecification);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if(liveTickerNewsSeriesBag.get()==null){
			TickerNewsSeries series=new TickerNewsSeries();
			liveTickerNewsSeriesBag.set(series);
		}
		liveTickerNewsSubscription.addEventListener(tickerNewsFeedListener);
		liveTickerNewsSubscription.activate();
		state.liveTickerNewsFeedActivated();
	}
	
	public void cancelLiveTickerNewsFeed() {
		if (liveTickerNewsSubscription != null) {
			liveTickerNewsSubscription.removeEventListener(tickerNewsFeedListener);
			liveTickerNewsSubscription.cancel();
		}
		state.liveTickerNewsFeedDeactivated();	
	}
	
	private final IEventListener<ArrayList<InstrumentSpecificationDetails>> instrumentSpecificationDetailsListener = new IEventListener<ArrayList<InstrumentSpecificationDetails>>() {
		public void eventFired(ArrayList<InstrumentSpecificationDetails> detailsList) {
			instrumentSpecificationDetails=detailsList;
			for(InstrumentSpecificationDetails details:instrumentSpecificationDetails){
				InstrumentSpecification spec=details.getInstrumentSpecification();
				assert(instrumentSpecification.getSymbol().toString().equals(spec.getSymbol().toString()));	
				if(useDatabaseForSpecDetails){
					details=ema.getSpecificationDetailsDao().update(details);
				}		
			}			
			state.setSpecDetailsRequestReceived(detailsList.size()!=0);
		}
	};

	public void requestInstrumentSpecificationDetails() {
		if(this.instrumentSpecification.hasId()&&this.instrumentSpecificationDetails!=null&&this.instrumentSpecificationDetails.size()!=0&&this.instrumentSpecificationDetails.get(0).hasId()){
			//no need to do this again, I got specDetails from the MarketScan
			return;
		}
		InstrumentSpecificationDetails details=null;
		if(useDatabaseForSpecDetails){//look up in database in future
			details=ema.getSpecificationDetailsDao().findByInstrumentSpecification(instrumentSpecification);
		}
		if(details==null){
			state.standInLineForSpecDetailsRequest();
			try {
				ema.getContractDetailsSource().requestInstrumentSpecificationDetails(instrumentSpecification, instrumentSpecificationDetailsListener);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			state.waitSpecDetailsFinish();
		}else{
			instrumentSpecificationDetails=new ArrayList<InstrumentSpecificationDetails>();
			instrumentSpecificationDetails.add(details);
		}
	}
	/**
	 * Returns whether I have more than 1 instrumentSpecDetails for the associated instrumentSpecification(InstrumentSpecification)
	 * @return
	 */
	public boolean gotMultipleInstrumentSpecsDetails(){
		if(instrumentSpecificationDetails.size()>1){
			log.info("Got more than one specDetails for "+instrumentSpecification.getSymbol().toString()+ " ("+instrumentSpecificationDetails.size()+")");
			return true;
		}
		return false;		
	}
	
//	public void calculateT_MINUS_1Y_BollingerBands(){
//		analyzerUtils.calculateBollingerBands(getT_MINUS_1Y_HistoryCandles(), 20, 2);
//	}
		
//	public void calculateT_MINUS_1Y_SAR(){
//		analyzerPlugin.calculateSAR(getT_MINUS_1Y_HistoryCandles());
//	}
	
//	public void calculateT_MINUS_1Y_WMA(){
//		analyzerPlugin.calculateWMA(getT_MINUS_1Y_HistoryCandles(),15);
//	}
	
//	public void calculateT_MINUS_1Y_EMA(){
//		analyzerPlugin.calculateEMA(getT_MINUS_1Y_HistoryCandles(),15);
//	}
	
//	public void calculateT_MINUS_1Y_MEMA(){
//		analyzerPlugin.calculateMEMA(getT_MINUS_1Y_HistoryCandles(),15);
//	}
	
	/**
	 * synchronized method, sets the associated enabled(boolean) with the given enabled(boolean)
	 * @param enabled
	 */
	private synchronized void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}
	/**
	 * Returns the associated enabled(boolean). Synchronized method
	 * @return true if session is enabled, false otherwise.
	 */
	public synchronized boolean isEnabled() {
		return enabled;
	}
	/**
	 * returns the associated log(Logger)
	 * @return
	 */
	public Logger getLog() {
		return log;
	}
	/**
	 * returns the associated state(InstrumentAnalyzerState)
	 * @return
	 */
	public InstrumentAnalyzerState getState(){
		return state;
	}
	/**
	 * returns the associated positionService(IPositionService)
	 * @return
	 */
	public IPositionService getPositionService(){
		return positionService;
	}
	/**
	 * returns the associated positionTracker(IPositionTracker)
	 * @return
	 */
	public IPositionTracker getPositionTracker(){
		return positionTracker;
	}
	/**
	 * sets the associated positionTracker(IPositionTracker) with the given one
	 * @return
	 */
	public void setPositionTracker(IPositionTracker positionTracker){
		this.positionTracker=positionTracker;
	}
	/**
	 * sets the associated positionService(IPositionService) with the given positionService(IPositionService)
	 * @param positionService
	 */
//	public void setPositionService(IPositionService positionService){
//		this.positionService=positionService;
//	}	
	/**
	 * returns the associated instrumentAnalyzers(Map&lt;InstrumentSpecification,InstrumentAnalyzer&gt;)
	 * @return
	 */
	public static Map<InstrumentSpecification, InstrumentAnalyzer> getInstrumentAnalyzers() {
		return instrumentAnalyzers;
	}	
	/**
	 * returns the associated position(IBPosition)
	 * @return
	 */
//	public IBPosition getPosition(){
//		return position;
//	}
	/**
	 * returns the associated instrumentSpecification(InstrumentSpecification)
	 * @return
	 */
	public InstrumentSpecification getInstrumentSpecification() {
		return instrumentSpecification;
	}
	
	/**
	 * returns the first of the associated instrumentSpecificationDetails(ArrayList&lt;InstrumentSpecificationDetails&gt;)
	 * @return
	 */
	public InstrumentSpecificationDetails getInstrumentSpecificationDetails() {
		try{
			return instrumentSpecificationDetails.get(0);
		}catch(Exception e){
			throw new RuntimeException("no instrumentSpecDetails has been set at this point");
		}
	}
	/**
	 * Resets/sets the associated instrumentSpecificationDetails(ArrayList&lt;InstrumentSpecificationDetails&gt;) with the given specDetails(InstrumentSpecificationDetails)
	 * @return
	 */
	public void setInstrumentSpecificationDetails(InstrumentSpecificationDetails specDetails) {
		
		this.instrumentSpecificationDetails=new ArrayList<InstrumentSpecificationDetails>();
		this.instrumentSpecificationDetails.add(specDetails);
	}
	
	/**
	 * returns the associated ema(EMA)
	 * @return
	 */
	public TraderProxy getEMA() {
		return ema;
	}
	/**
	 * returns the associated liveCandleSeriesBag(AtomicReference&lt;CandleSeries&gt;)
	 * @return
	 */
	public AtomicReference<CandleSeries> getLiveCandleSeriesBag() {
		return liveCandleSeriesBag;
	}
	/**
	 * returns the associated liveQuoteSeriesBag(AtomicReference&lt;QuoteSeries&gt;)
	 * @return
	 */
	public AtomicReference<QuoteSeries> getLiveQuoteSeriesBag() {
		return liveQuoteSeriesBag;
	}
	/**
	 * returns the associated liveMarketDepthEventSeriesBag(AtomicReference&lt;MarketDepthEventSeries&gt;)
	 * @return
	 */
	public AtomicReference<MarketDepthEventSeries> getLiveMarketDepthEventSeriesBag() {
		return liveMarketDepthEventSeriesBag;
	}
	/**
	 * returns the associated liveTradeIndicationSeriesBag(AtomicReference&lt;TradeIndicationSeries&gt;)
	 * @return
	 */
	public AtomicReference<TradeIndicationSeries> getLiveTradeIndicationSeriesBag() {
		return liveTradeIndicationSeriesBag;
	}
	/**
	 * returns the associated analyzerPlugin(AnalyzerPluginInterface)
	 * @return
	 */
	public AnalyzerPluginInterface getAnalyzerPlugin() {
		return analyzerPlugin;
	}
	/**
	 * returns the associated exchangeCalendar(IExchangeCalendar)
	 * @return
	 */
	public IExchangeCalendar getExchangeCalendar() {
		return exchangeCalendar;
	}
	
	public static Map<MarketScanSeriesSpecification,MarketScanSeries> getHistoricalMarketScanCache(){
		return historicalMarketScanCache;
	}
	
	public Map<MarketScanSeriesSpecification,MarketScanSeries> getHistoricalMarketScanCacheForThisInstrument(){
		return historicalMarketScanCacheForThisInstrument;
	}
	
	public void historicalDataProcessing(){
		
		

		
		
		
		
		
//		analyzerPlugin.draw1MCandles(false, false, false, 1); this draws the charts
//		analyzerPlugin.initializeTMINUS5DWeightedDayFeatures();//just to initialize optimalProfitTarget and timeToWaitOnExecution
//		getLog().info("current time :"+InstrumentAnalyzer.fmt.format(ema.getTimeStampService().currentTimeStamp()));
		
	}
	
	/**
	 * This gets called once for each instrument at the beginning<br/>
	 * If there was a Position for this Instrument and the profit goal has been reached and no closing order has been submitted, this will submit an Order to close the position and lock in Profits
	 */
	public void closePositionIfProfitGoalReached(){
		if(!(this.getPositionService() instanceof ClosePositionService)){
			return;
		}
		if(getEMA().getBroker() instanceof IBPortfolioManagingBroker){
			IBPortfolioManagingBroker broker =(IBPortfolioManagingBroker)getEMA().getBroker();
			Portfolio portfolio=broker.getBrokerAccount().getPortfolio();
			IBPosition position=portfolio.hasPosition(instrumentSpecification) ? (IBPosition)portfolio.getPosition(instrumentSpecification) : null;
			if(position==null || !IBPortfolioManagingBroker.hasPositionReachedDesiredUnrealizedPL(position)){
				return;
			}
			//double averagePrice=position.getAveragePrice();//what I paid including commissions
			double quantity = position.getQuantity();
//			double realizedPNL=0;
//			double unrealizedPNL=0;
//			double marketPrice=0;
//			double marketValue=0;
//			try{
//				realizedPNL=Double.parseDouble(position.getAdditionalInformation().get(IBPortfolioManagingBroker.REALIZED_PNL));
//			}catch(Exception e){
//			}
//			try{
//				unrealizedPNL=Double.parseDouble(position.getAdditionalInformation().get(IBPortfolioManagingBroker.UNREALIZED_PNL));
//			}catch(Exception e){
//			}
//			try{
//				marketPrice=Double.parseDouble(position.getAdditionalInformation().get(IBPortfolioManagingBroker.MARKET_PRICE));
//			}catch(Exception e){
//			}
//			try{
//				marketValue=Double.parseDouble(position.getAdditionalInformation().get(IBPortfolioManagingBroker.MARKET_VALUE));
//			}catch(Exception e){
//			}
			
			if(quantity>0){
				//long need to sell				
				Order sellOrder = new Order();
				sellOrder.setInstrumentSpecification(this.getInstrumentSpecification());
				sellOrder.setOrderTimeStamp(ema.getTimeStampService().currentTimeStamp());
				sellOrder.setOrderSide(OrderSide.SELL);
				sellOrder.setOrderType(OrderType.MARKET);
				sellOrder.setQuantity(quantity);
				//sellOrder.setLimitPrice(askPrice);			
				if(!isOrderAlreadySubmitted(sellOrder)){
//					this.setPositionService(new ClosePositionService(ema));
					this.getPositionService().openPosition(sellOrder);								
				}else{
					log.info("An identical Order is already opened");
				}
			}else if(quantity<0){
				//short, need to buy
				Order sellOrder = new Order();
				sellOrder.setInstrumentSpecification(this.getInstrumentSpecification());
				sellOrder.setOrderTimeStamp(ema.getTimeStampService().currentTimeStamp());
				sellOrder.setOrderSide(OrderSide.BUY);
				sellOrder.setOrderType(OrderType.MARKET);
				sellOrder.setQuantity(Math.abs(quantity));
				//sellOrder.setLimitPrice(askPrice);				
				if(!isOrderAlreadySubmitted(sellOrder)){
//					this.setPositionService(new ClosePositionService(ema));
					this.getPositionService().openPosition(sellOrder);								
				}else{
					log.info("An identical Order is already opened");
				}
			}
			//position.getPriceDifference(quote)
		}
	}
	/**
	 * This returns whether a Not Completed Order with the same characteristics as the given order(Order) is already in the  broker orders
	 * @param order
	 * @return
	 */
	public boolean isOrderAlreadySubmitted(Order order){
		IOrderTracker[] orderTrackers= ema.getBroker().getOrders();
		for(IOrderTracker orderTracker : orderTrackers){
			if(orderTracker.getOrderCompletion()!=null)continue;			
			Order anOrder = orderTracker.getOrder();
			InstrumentSpecification aSpec=anOrder.getInstrumentSpecification();
			if(!aSpec.equals(order.getInstrumentSpecification()))continue;
			if(order.getOrderSide()==anOrder.getOrderSide()&&
			   order.getOrderType()==anOrder.getOrderType()&&
			   order.getTimeInForce()==anOrder.getTimeInForce()&&
			   order.getQuantity()==anOrder.getQuantity()&&
			   order.getTrailingDistance()==anOrder.getTrailingDistance()&&
			   order.getLimitPrice()==anOrder.getLimitPrice()&&
			   order.getStopPrice()==anOrder.getStopPrice()
			){return true;}
		}
		return false;
	}
	/**
	 * If I haven't already decided to buy and conditions are right, buy
	 */
	public void checkAndOpenPositionIfConditionsAreRight(){
		if(hasRequestedToGoLong.get())return;//we already decided to open position once, don't buy once again
		
//		if(position!=null)return;//we already have a position, don't go deeper?
		
		//if(analyzerUtils.latestDailyRelativeStrengthIndex.get()!=RelativeStrengthIndex.OVERSOLD||analyzerUtils.isLatestCandleHammer2.get()) return;
		
		//log.info("isLatestCandleHammer2="+analyzerUtils.isLatestCandleHammer2.get());
		if(liveCandleSeriesBag==null||liveCandleSeriesBag.get()==null||liveCandleSeriesBag.get().size()==0){
			log.info("waiting for live Candle");
			return;
		}
		//MarketDepthSeries marketDepthSeries = null;
//		if(liveMarketDepthSeriesBag==null){
//			log.info("waiting for live MarketDepth");
//			return;
//		}else{
//			//marketDepthSeries = liveMarketDepthSeriesBag.get();
//			//if(marketDepthSeries==null||marketDepthSeries.size()==0){
//			//	log.info("waiting for live MarketDepth");
//			//	return;
//			//}
//		}
		if(liveQuoteSeriesBag==null||liveQuoteSeriesBag.get()==null||liveQuoteSeriesBag.get().size()==0){
			log.info("waiting for live Quote");
			return;
		}
		
		if(liveTradeIndicationSeriesBag==null||liveTradeIndicationSeriesBag.get()==null||liveTradeIndicationSeriesBag.get().size()==0){
			log.info("waiting for live TradeIndication");
			return;
		}
		Order buyOrder = new Order();
		buyOrder.setInstrumentSpecification(InstrumentAnalyzer.this.instrumentSpecification);
		buyOrder.setOrderTimeStamp(ema.getTimeStampService().currentTimeStamp());
		buyOrder.setOrderSide(OrderSide.BUY);
		buyOrder.setOrderType(OrderType.LIMIT);
		
		Candle latestCandle=liveCandleSeriesBag.get().get(0);
		//MarketDepth latestMarketDepth=marketDepthSeries.get(0);
		Quote latestQuote=liveQuoteSeriesBag.get().get(0);
		TradeIndication latestTradeIndication=liveTradeIndicationSeriesBag.get().get(0);
		//double lowestAskPriceOffer=latestMarketDepth.getAskDepth().get(0).getPrice();
		
		double askPrice=FinancialLibrary.minOf(latestCandle.getClosePrice(), latestQuote.getAskPrice(), latestTradeIndication.getPrice());
		//askPrice=FinancialLibrary.minOf(askPrice, lowestAskPriceOffer);
		double optimalQuantity = CommissionsCalculator.determineOrderQuantityForOptimalMinimumCommission(buyOrder);
//		buyOrder.setQuantity(optimalQuantity);
		buyOrder.setQuantity(optimalQuantity);
		//double approximateCommission = CommissionsCalculator.determineApproximateCommissionForOrder(buyOrder);
		//log.info("approximateCommission="+approximateCommission+" quantity="+optimalQuantity);
		buyOrder.setLimitPrice(askPrice);
		InstrumentAnalyzer.this.getPositionService().openPosition(buyOrder);
		hasRequestedToGoLong.set(true);
	}
	
	public static String formatTimeStamp(TimeStamp timeStamp){
		return fmt.format(timeStamp);
	}
}
